Skip to main content

Copy vs Move

When you assign a value, pass it to a function, or return it, Rust must decide:

Do I copy the value, or do I move ownership?

Rust answers this at compile time, based on the type.

Copy Semantics

A type is Copy if:

  • Its value can be duplicated bit-for-bit
  • Copying is cheap and safe
  • No heap memory needs to be managed

Common Copy Types

  • Integers: i32, u64
  • Floating point: f32, f64
  • bool, char
  • Tuples of Copy types
  • References (&T, &mut T are Copy, not the data!)
fn main() {
let x = 5;
let y = x;

println!("{}", x); // ✅ still valid
}

What happens in memory

  • x lives on the stack
  • y gets a full copy of the bits
  • Both values are independent
  • No ownership transfer

Think: photocopying a number

Copy in Function Calls

fn takes_i32(n: i32) {
println!("{}", n);
}

let x = 10;
takes_i32(x);
println!("{}", x); // ✅ OK

Move Semantics

A move happens when:

  • A value owns heap memory
  • Copying would risk double free
  • Ownership must be transferred

After a move:

  • The original variable is invalid
  • The new variable is the sole owner
fn main() {
let s1 = String::from("hello");
let s2 = s1;

println!("{}", s1); // ❌ compile-time error
}

What happens in memory

Stack before move:      Stack after move:
s1 ─┐ s2 ─┐
└─→ Heap "hello" └─→ Heap "hello"
  • Only the pointer is moved
  • Heap data is NOT copied
  • s1 is invalidated

This prevents double free.

Move in Function Calls

fn takes_string(s: String) {
println!("{}", s);
}

let s = String::from("hello");
takes_string(s);
println!("{}", s); // ❌ error

Ownership moves into the function.

Why Rust Defaults to Move

Imagine Rust copied heap data automatically:

let s1 = String::from("hello");
let s2 = s1; // deep copy?

Problems:

  • Expensive
  • Unexpected performance cost
  • Double-free risk if shallow copy

Rust chooses:

Move by default, copy only when explicitly safe

Clone vs Copy (Very Important)

Copy – implicit, cheap, automatic

let x = 5;
let y = x; // copy

Clone – explicit, potentially expensive

let s1 = String::from("hello");
let s2 = s1.clone();

println!("{}", s1); // ✅ OK
  • Allocates new heap memory
  • Copies the data
  • You must ask for it

Rust makes you be explicit about cost.

Implementing Copy

A type can be Copy only if all its fields are Copy.

#[derive(Copy, Clone)]
struct Point {
x: i32,
y: i32,
}

Now:

let p1 = Point { x: 1, y: 2 };
let p2 = p1;
println!("{}, {}", p1.x, p2.x); // ✅

But this is illegal:

struct Bad {
s: String, // ❌ not Copy
}

References: Copy but Not Ownership

let s = String::from("hello");
let r1 = &s;
let r2 = r1; // copy of reference
  • r1 and r2 both point to s
  • Ownership is unchanged
  • Borrowing rules still apply

Partial Moves (Advanced but Useful)

struct User {
name: String,
age: u32,
}

let user = User {
name: String::from("Alice"),
age: 30,
};

let name = user.name; // move
println!("{}", user.age); // ✅ OK
  • name moved
  • age still accessible
  • user is partially invalid

Moves in Pattern Matching

let s = Some(String::from("hello"));

match s {
Some(value) => println!("{}", value), // move
None => {}
}

println!("{:?}", s); // ❌ error

Fix with borrowing:

match &s {
Some(value) => println!("{}", value),
None => {}
}

Copy vs Move Summary Table

AspectCopyMove
Implicit
Heap-safe
Performance costTinyTiny
Heap allocation
Ownership transfer
Original usable

Mental Model (This Sticks)

  • Copy = duplicate bits
  • Move = transfer responsibility
  • Clone = deep copy (explicit)
  • Ownership = who frees the heap

Rust doesn’t guess.
If it’s expensive or dangerous, you must say so.

Why This Matters for Memory Safety

Without move semantics:

  • Double free bugs
  • Use-after-free
  • Hidden performance costs

Rust prevents all of these at compile time.